/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package BusinessLogic;

import Entity.*;
import EntityState.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Resource;
import javax.ejb.Stateless;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import org.apache.commons.codec.digest.DigestUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
/**
 *
 * @author James
 */
@Stateless
public class CustomerBean implements CustomerBeanLocal {
    //Resources
    @Resource(mappedName = "jms/QueueConnectionFactory")
    private ConnectionFactory queueConnectionFactory;
    @Resource(mappedName = "jms/OutgoingMailQueue")
    private Queue emailQueue;
    @PersistenceContext(unitName = "TCube-ejbPU")
    private EntityManager em;

    //Public methods of Customer Bean:
    @Override
    public ArrayList reserve(String reservableUnitID, String username, String name, String countryCode, String phone, int numOfPeople, long startTime, long endTime, long[] listOfVouchers) {
        ArrayList list = new ArrayList();
        
        ReservableUnit ru = em.find(ReservableUnit.class, reservableUnitID);
        Customer       c  = em.find(Customer.class, username);
        if (ru == null || c == null) list.add(Constants.STATUS_NOT_FOUND);
        else {
            DateTimeZone customerZone = DateTimeZone.forID(c.getTimezone());
            long current = new DateTime(customerZone).getMillis();
            ReservableUnitDetails details = ru.getLatestReservableUnitDetails();
            
            if (current - startTime < details.getReservationMinimumLeadTime()*60000
            ||  endTime - current   > details.getReservationMaximumLeadTime()*60000
            ||  endTime - startTime < details.getReservableMinimumLength()*60000
            ||  endTime - startTime > details.getReservableMaximumLength()*60000) list.add(Constants.STATUS_INVALID_INPUT);
            else {
                Outlet o = ru.getOutlet();
                // Calculate how many units are needed to handle numOfPeople
                int numOfUnits = numOfPeople / details.getNumOfCustomersPerUnit();
                if (numOfPeople % details.getNumOfCustomersPerUnit() != 0) numOfUnits++;
                // Check if the slot if available and decrease the number of available reservableUnits
                DateTimeZone outletZone = DateTimeZone.forID(o.getTimezone());
                DateTime start = new DateTime(startTime, customerZone).withZone(outletZone);
                DateTime end   = new DateTime(endTime, customerZone).withZone(outletZone);
                boolean slotAvailable = true;
                while (start.isBefore(end)) {
                    int month   = start.getMonthOfYear();
                    int day     = start.getDayOfMonth();
                    int hour    = start.getHourOfDay();
                    int quarter = start.getMinuteOfHour()/Constants.RESERVATION_NUM_OF_MINUTES_PER_SLOT;
                    int[][][] monthArray = ru.getMonth(month);
                    if (monthArray[day-1][hour][quarter] >= numOfUnits) monthArray[day-1][hour][quarter] -= numOfUnits;
                    else {
                        slotAvailable = false;
                        break;
                    }
                    start = start.plusMinutes(Constants.RESERVATION_NUM_OF_MINUTES_PER_SLOT);
                }
                // If available, block the slot and create the reservation
                if (slotAvailable) {
                    // Create the reservation
                    Reservation r = new Reservation();
                    // Bind all the vouchers to this reservation and add to the VoucherState list
                    List<VoucherState> vouchers = new ArrayList<VoucherState>();
                    for (long voucherID : listOfVouchers) {
                        // Bind to reservation
                        Voucher v = em.find(Voucher.class, voucherID);
                        r.addVoucher(v);
                        // Add to Voucher list
                        VoucherState vs = new VoucherState(v.getId(), v.getName());
                        vouchers.add(vs);
                    }
                    // Specify other necessary fields
                    if (name.equals("")) r.createReservation(startTime, endTime, c.getLastName() + ", " + c.getFirstName(), c.getCountryCode(), c.getPhone(), numOfPeople, c, ru);
                    else r.createReservation(startTime, endTime, name, countryCode, phone, numOfPeople, c, ru);
                    em.persist(r);
                    c.addReservation(r);
                    ru.addReservation(r);
                    
                    // Create the reservationState to put in cart
                    Item i = ru.getReservedItem();
                    ItemState  reservedItem = new ItemState(i.getId(), i.getName());
                    OutletState   theOutlet = new OutletState(o.getId(), o.getName());
                    ReservableUnitState rus = new ReservableUnitState(ru.getId(), ru.getName(), theOutlet, reservedItem);
                    ReservationState     rs = new ReservationState(r.getId(), r.getStartTime(), r.getEndTime(), r.getCreatedDate(), r.getCustomerName(), r.getCountryCode(), r.getPhone(), r.getNumOfPeople(), rus, vouchers);
                    list.add(Constants.STATUS_SUCCESSFUL);
                    list.add(rs);
                } else {
                    // Reverse the blocking
                    end = start;
                    start = new DateTime(startTime, customerZone).withZone(outletZone);
                    while (start.isBefore(end)) {
                        int month   = start.getMonthOfYear();
                        int day     = start.getDayOfMonth();
                        int hour    = start.getHourOfDay();
                        int quarter = start.getMinuteOfHour()/Constants.RESERVATION_NUM_OF_MINUTES_PER_SLOT;
                        int[][][] monthArray = ru.getMonth(month);
                        monthArray[day-1][hour][quarter] += numOfUnits;
                        start = start.plusMinutes(Constants.RESERVATION_NUM_OF_MINUTES_PER_SLOT);
                    }
                    // return result to customer
                    list.add(Constants.STATUS_FAILED);
                }
            }
        }
        
        return list;
    }
    
    @Override
    public ArrayList getInfo(int action, long id) {
        ArrayList list = new ArrayList();
        
        if (action == Constants.GET_PARTNER_PROFILE) {
            Partner p = em.find(Partner.class, id);
            if (p == null) list.add(Constants.STATUS_NOT_FOUND);
            else {
                PartnerState ps = new PartnerState(p.getId(), p.getName(), p.getBusinessAddress1(), p.getBusinessAddress2(), p.getBusinessPostalCode(), p.getBillingAddress1(), p.getBillingAddress2(), p.getBillingPostalCode(), p.getCountryCode(), p.getPhone(), p.getCountry(), p.getProvince(), p.getCity(), p.getTimezone(), p.getBusinessEmail(), p.getNotificationURL(), p.getRegisteredDate(), p.getSector(), p.getRatedPoint(), p.getNumOfRater(), p.getReservationNoShowFee(), p.getReservationCancelFee(), p.getReservableMinimumLength(), p.getReservableMaximumLength(), p.getReservationMinimumLeadTime(), p.getReservationMaximumLeadTime());
                list.add(Constants.STATUS_SUCCESSFUL);
                list.add(ps);
            }
            
        } else if (action == Constants.GET_LIST_OF_BRANCHES) {
            Partner p = em.find(Partner.class, id);
            if (p == null) list.add(Constants.STATUS_NOT_FOUND);
            else {
                List<Partner> listOfBranches = p.getListOfBranches();
                List<PartnerState> branches = new ArrayList<PartnerState>();
                for (Partner branch : listOfBranches) {
                    PartnerState ps = new PartnerState(branch.getId(), branch.getName(), branch.getSector(), branch.getRatedPoint(), branch.getNumOfRater());
                    branches.add(ps);
                }
                list.add(Constants.STATUS_SUCCESSFUL);
                list.add(branches);
            }
            
        } else if (action == Constants.GET_LIST_OF_OUTLETS) {
            Partner p = em.find(Partner.class, id);
            if (p == null) list.add(Constants.STATUS_NOT_FOUND);
            else {
                List<Outlet> listOfOutlets = p.getListOfOutlets();
                List<OutletState> outlets = new ArrayList<OutletState>();
                for (Outlet outlet : listOfOutlets) {
                    if (outlet.isActive() || !outlet.isDeleted()) {
                        OutletState os = new OutletState(outlet.getId(), outlet.getName());
                        outlets.add(os);
                    }
                }
                list.add(Constants.STATUS_SUCCESSFUL);
                list.add(outlets);
            }
            
        } else if (action == Constants.GET_OUTLET_PROFILE) {
            Outlet o = em.find(Outlet.class, id);
            if (o == null || !o.isActive() || o.isDeleted()) list.add(Constants.STATUS_NOT_FOUND);
            else {
                // Get the layout
                Files layout = o.getLayout();
                FileState layoutState = new FileState(layout.getId(), layout.getName(), layout.getLocation(), layout.getDescription(), layout.getCreatedDate());
                
                // Get list of pictures
                List<Files> listOfPictures = o.getListOfPictures();
                List<FileState> pictures   = new ArrayList<FileState>();
                for (Files picture : listOfPictures) {
                    FileState fs = new FileState(picture.getId(), picture.getName(), picture.getLocation(), picture.getDescription(), picture.getCreatedDate());
                    pictures.add(fs);
                }
                
                OutletState os = new OutletState(o.getId(), o.getName(), o.getAddress1(), o.getAddress2(), o.getPostalCode(), o.getCountryCode(), o.getPhone(), o.getCountry(), o.getProvince(), o.getCity(), o.getTimezone(), o.getBusinessEmail(), layoutState, pictures);
                list.add(Constants.STATUS_SUCCESSFUL);
                list.add(os);
            }
            
        } else if (action == Constants.GET_LIST_OF_PARTNER_CATEGORIES) {
            Partner p = em.find(Partner.class, id);
            if (p == null) list.add(Constants.STATUS_NOT_FOUND);
            else {
                List<ItemCategory> listOfItemCategories = p.getItemCategories();
                List<ItemCategoryState> categories = new ArrayList<ItemCategoryState>();
                for (ItemCategory category : listOfItemCategories) {
                    if (category.isActive() && !category.isDeleted()) {
                        ItemCategoryState ics = new ItemCategoryState(category.getId(), category.getName(), category.getDescription(), category.getDiscount(), category.getCreatedDate());
                        categories.add(ics);
                    }
                }
                list.add(Constants.STATUS_SUCCESSFUL);
                list.add(categories);
            }
            
        } else if (action == Constants.GET_LIST_OF_OUTLET_CATEGORIES) {
            Outlet o = em.find(Outlet.class, id);
            if (o == null || !o.isActive() || o.isDeleted()) list.add(Constants.STATUS_NOT_FOUND);
            else {
                List<ItemCategory> listOfItemCategories = o.getItemCategories();
                List<ItemCategoryState> categories = new ArrayList<ItemCategoryState>();
                for (ItemCategory category : listOfItemCategories) {
                    if (category.isActive() && !category.isDeleted()) {
                        ItemCategoryState ics = new ItemCategoryState(category.getId(), category.getName(), category.getDescription(), category.getDiscount(), category.getCreatedDate());
                        categories.add(ics);
                    }
                }
                list.add(Constants.STATUS_SUCCESSFUL);
                list.add(categories);
            }
            
        } else if (action == Constants.GET_ITEM_CATEGORY_INFO) {
            ItemCategory ic = em.find(ItemCategory.class, id);
            if (ic == null || ic.isDeleted() || !ic.isActive()) list.add(Constants.STATUS_NOT_FOUND);
            else {
                List<Item> listOfItems = ic.getListOfItems();
                List<ItemState> items  = new ArrayList<ItemState>();
                for (Item item : listOfItems) {
                    if (!item.isDeleted() && item.isActive()) {
                        ItemDetailsState ids = null;
                        for (ItemDetails details : item.getListOfItemDetails()) {
                            if (details.getStoppedDate() == 0) {
                                ids = new ItemDetailsState(details.getId(), details.getDiscount(), details.getPrice(), details.getRedeemPrice(), details.getAdditionalLoyaltyPoint(), details.getStartedDate());
                                break;
                            }
                        }
                        ItemState is = new ItemState(item.getId(), item.getName(), item.getDescription(), item.getLastMonthNumOfBuyers(), item.getNumOfBuyers(), item.getCreatedDate(), ids);
                        items.add(is);
                    }
                }
                ItemCategoryState ics = new ItemCategoryState(ic.getId(), ic.getName(), ic.getDescription(), ic.getDiscount(), ic.getCreatedDate(), items);
                list.add(Constants.STATUS_SUCCESSFUL);
                list.add(ics);
            }
            
        } else if (action == Constants.GET_DETAILED_ITEM_INFO) {
            Item i = em.find(Item.class, id);
            if (i == null || i.isDeleted() || !i.isActive()) list.add(Constants.STATUS_NOT_FOUND);
            else {
                // Get the latest item details
                ItemDetailsState ids = null;
                for (ItemDetails details : i.getListOfItemDetails()) {
                    if (details.getStoppedDate() == 0) {
                        List<Item> listOfSubItems = details.getSubItems();
                        List<ItemState> subItems  = new ArrayList<ItemState>();
                        for (Item subItem : listOfSubItems) {
                            ItemState item = new ItemState(subItem.getId(), subItem.getName());
                            subItems.add(item);
                        }
                        ids = new ItemDetailsState(details.getId(), details.getDiscount(), details.getPrice(), details.getRedeemPrice(), details.getAdditionalLoyaltyPoint(), details.getStartedDate(), subItems);
                        break;
                    }
                }
                
                // Get list of pictures
                List<Files> listOfPictures = i.getListOfPictures();
                List<FileState> pictures   = new ArrayList<FileState>();
                for (Files picture : listOfPictures) {
                    FileState fs = new FileState(picture.getId(), picture.getName(), picture.getLocation(), picture.getDescription(), picture.getCreatedDate());
                    pictures.add(fs);
                }
                
                ItemState is = new ItemState(i.getId(), i.getName(), i.getDescription(), i.getLastMonthNumOfBuyers(), i.getNumOfBuyers(), i.getCreatedDate(), ids, pictures);
                list.add(Constants.STATUS_SUCCESSFUL);
                list.add(is);        
            }
            
        } else if (action == Constants.GET_SIMPLE_ITEM_INFO) {
            Item i = em.find(Item.class, id);
            if (i == null || i.isDeleted() || !i.isActive()) list.add(Constants.STATUS_NOT_FOUND);
            else {
                // Get the latest item details
                ItemDetailsState ids = null;
                for (ItemDetails details : i.getListOfItemDetails()) {
                    if (details.getStoppedDate() == 0) {
                        ids = new ItemDetailsState(details.getId(), details.getDiscount(), details.getPrice());
                        break;
                    }
                }
                
                ItemState is = new ItemState(i.getId(), i.getName(), ids);
                list.add(Constants.STATUS_SUCCESSFUL);
                list.add(is);        
            }
            
        } else if (action == Constants.GET_LIST_OF_RESERVABLE_UNITS) {
            Outlet o = em.find(Outlet.class, id);
            if (o == null || !o.isActive() || o.isDeleted()) list.add(Constants.STATUS_NOT_FOUND);
            else {
                List<ReservableUnit> listOfReservableUnits = o.getListOfReservationUnits();
                List<ReservableUnitState> reservableUnits  = new ArrayList<ReservableUnitState>();
                for (ReservableUnit unit : listOfReservableUnits) {
                    Item reservedItem;
                    if (!unit.isDeleted() && unit.isActive()) {
                        reservedItem = unit.getReservedItem();
                        ItemState is = new ItemState(reservedItem.getId(), reservedItem.getName());
                        ReservableUnitState rus = new ReservableUnitState(unit.getId(), unit.getName(), unit.getDescription(), is);
                        reservableUnits.add(rus);
                    }
                }
                list.add(Constants.STATUS_SUCCESSFUL);
                list.add(reservableUnits);
            }
        
        } else if (action == Constants.GET_RESERVABLE_UNIT_INFO) {
            ReservableUnit ru = em.find(ReservableUnit.class, id);
            if (ru == null || ru.isDeleted() || !ru.isActive()) list.add(Constants.STATUS_NOT_FOUND);
            else {
                // Get the latest reservableUnit details
                ReservableUnitDetailsState details = null;
                for (ReservableUnitDetails rud : ru.getListOfReservableUnitDetails()) {
                    if (rud.getStoppedDate() == 0) {
                        details = new ReservableUnitDetailsState(rud.getId(), rud.getReservationNoShowFee(), rud.getReservationCancelFee(), rud.getReservableMinimumLength(), rud.getReservableMaximumLength(), rud.getReservationMinimumLeadTime(), rud.getReservationMaximumLeadTime(), rud.getNumOfCustomersPerUnit());
                        break;
                    }
                }
                
                // Get the layout
                Files layout = ru.getLayout();
                FileState layoutState = new FileState(layout.getId(), layout.getName(), layout.getLocation(), layout.getDescription(), layout.getCreatedDate());
                
                // Get list of pictures
                List<Files> listOfPictures = ru.getListOfPictures();
                List<FileState> pictures   = new ArrayList<FileState>();
                for (Files picture : listOfPictures) {
                    FileState fs = new FileState(picture.getId(), picture.getName(), picture.getLocation(), picture.getDescription(), picture.getCreatedDate());
                    pictures.add(fs);
                }
                
                // Get reservedItem
                Item reservedItem = ru.getReservedItem();
                ItemState is = new ItemState(reservedItem.getId(), reservedItem.getName());
                
                ReservableUnitState rus = new ReservableUnitState(ru.getId(), ru.getName(), ru.getDescription(), layoutState, is, details, pictures);
                list.add(Constants.STATUS_SUCCESSFUL);
                list.add(rus);
            }
            
        } else if (action == Constants.GET_LIST_OF_RESERVATIONS) {
            Customer c = em.find(Customer.class, id);
            if (c == null) list.add(Constants.STATUS_NOT_FOUND);
            else {
                // Get list of reservations in the last 2 months + future
                List<Reservation> listOfReservations = c.getListOfReservations();
                List<ReservationState>  reservations = new ArrayList<ReservationState>();
                DateTime threeMonthsAgo = new DateTime(DateTimeZone.forID(c.getTimezone())).minusMonths(2);
                for (Reservation r : listOfReservations) {
                    DateTime date = new DateTime(r.getStartTime(), DateTimeZone.forID(c.getTimezone()));
                    if (date.isAfter(threeMonthsAgo)) {
                        ReservableUnit ru = r.getReservedUnit();
                        Item            i = ru.getReservedItem();
                        ItemState  reservedItem = new ItemState(i.getId(), i.getName());
                        OutletState   theOutlet = new OutletState(ru.getOutlet().getId(), ru.getOutlet().getName());
                        ReservableUnitState rus = new ReservableUnitState(ru.getId(), ru.getName(), theOutlet, reservedItem);
                        ReservationState     rs = new ReservationState(r.getId(), r.getStartTime(), r.getEndTime(), r.getCreatedDate(), r.getNumOfPeople(), r.getAmountPaid(), rus);
                        reservations.add(rs);
                    }
                }
                list.add(Constants.STATUS_SUCCESSFUL);
                list.add(reservations);
            }
        }  
        
        return list;
    }
    
    @Override
    public int checkAvailability(String username) {
        if (em.find(Customer.class, username) == null) return Constants.STATUS_SUCCESSFUL;
        else return Constants.STATUS_FAILED;
    }

    @Override
    public int registerAccount(String username, String password, String IC, String firstName, String lastName, String gender, String countryCode, String phone, String email, String country, String city, long birthday) {
        //Check if the database contains user with same NRIC or email
        Query q = em.createQuery("SELECT C FROM Customer C WHERE C.IC=:IC OR C.email=:email OR C.username=:username");
        q.setParameter("IC", IC);
        q.setParameter("email", email);
        q.setParameter("username", username);

        //If this user is unique, create an entry
        if (!q.getResultList().isEmpty()) return Constants.STATUS_NOT_UNIQUE;
        else {
            Customer u = new Customer();
            u.createUser(username, DigestUtils.sha256Hex(password), IC, firstName, lastName, gender, countryCode, phone, email, country, city, birthday);
            String id = getUniqueID();
            while (em.find(InactivatedUser.class, id) != null) id = getUniqueID(); 
            InactivatedUser iu = new InactivatedUser(id, u);
            em.persist(iu);
            em.flush();
            String link = "http://localhost:8080/portal/public/Activate?id=" + id; 
            sendEmail(Constants.MAIL_ACTIVATION, email, link);
            return Constants.STATUS_SUCCESSFUL;
        }
    }
    
    @Override
    public int activateUser(String id) {
        InactivatedUser iu = em.find(InactivatedUser.class, id);
        if (iu == null) return Constants.STATUS_NOT_FOUND;
        else {
            iu.getUser().setActivated(true);
            em.remove(iu);
            return Constants.STATUS_SUCCESSFUL;
        }
    }

    @Override
    public CustomerState login(String username, String password, String timezone) {
        Customer u = em.find(Customer.class, username);
        if (u == null || !u.getPassword().equals(DigestUtils.sha256Hex(password))) return null;
        else {
            CustomerState us;
            if (u.isActive() && u.isActivated()) {
                // Update timezone everytime a user log in
                if (!u.getTimezone().equals(timezone)) u.setTimezone(timezone);
                us = new CustomerState(u.getIC(), u.getFirstName(), u.getLastName(), u.getGender(), u.getTimezone(), u.getAddress1(), u.getAddress2(), u.getCountryCode(), u.getPhone(), u.getEmail(), u.getCountry(), u.getCity(), username, u.getLoyaltyPoints(), u.isActive(), u.isActivated(), u.getBirthday(), u.getRegisteredDate());
            } else us = new CustomerState(u.isActive(), u.isActivated());
            return us;
        }
    }

    

    //Private methods of Customer Bean:
    private String getUniqueID() {
        int NUM_CHARS = 20;
        String chars = "abcdefghijklmonpqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        Random r = new Random();
        char[] buf = new char[NUM_CHARS];

        for (int i = 0; i < buf.length; i++) {
            buf[i] = chars.charAt(r.nextInt(62));
        }

        return new String(buf);
    }
    
    private void sendEmail(int msgType, String recipient, String link, String... args) {
        try {
            Connection      queueConnection = queueConnectionFactory.createConnection();
            Session         queueSession    = queueConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            MessageProducer queueProducer   = queueSession.createProducer(emailQueue);
            MapMessage      msg             = queueSession.createMapMessage();
            //Plug in all data available and send the message
            msg.setInt("msgType", msgType);
            msg.setString("recipient", recipient);
            if (link != null) msg.setString("link", link);
            int i = 1;
            for (String str : args) {
                msg.setString("str"+i, str);
                i++;
            }
            queueProducer.send(msg);
            queueConnection.close();
        } catch (JMSException ex) {
            Logger.getLogger(CustomerBean.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}
